feat(emitters): add the dotnet facts emitter and centralize config globs - #5
Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub. |
|
All alerts resolved. Learn more about Socket for GitHub. This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored. |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub. |
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issue.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 93ea26c. Configure here.
| ) | ||
| return await assembleFromRecords(out, recordsFile) | ||
| }) | ||
| } |
There was a problem hiding this comment.
Dotnet path ignores excludePaths
Medium Severity
runDotnet never forwards excludePaths, and the C# tool has no exclude-path option. Callers that rely on the shared FactsGenerationOptions.excludePaths contract get a full .NET project graph anyway, unlike Gradle, Maven, and sbt.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 93ea26c. Configure here.
Bring .NET/NuGet to the facts pipeline at parity with the JVM emitters. Emitter: - Bundled C# tool (socket-facts-dotnet) running one MSBuild session — evaluate, in-process restore, read project.assets.json through NuGet.ProjectModel — emitting the shared records TSV. Ships no NuGet or MSBuild runtime assemblies: it compiles low and runs high against the locator-selected SDK (net6 floor, RollForward LatestMajor), so it works across installed SDK versions and avoids the ref/def assembly clash that breaks multi-SDK hosts. Restore forces TreatWarningsAsErrors=false so NU19xx security advisories do not abort the run. packages.config is supported: flat pinned closure, developmentDependency to dev, HintPath DLLs, and pinned-artifact download through the project's configured feeds. Fail-closed: it records failures instead of throwing, and every emitted artifact path is guaranteed to exist. - runFactsGeneration accepts tool: 'dotnet'; assets.mts resolves the published tool and throws when a packaging defect leaves it out. - NuGet resolution dialect, including the noun a .NET user expects: the report says "target framework" and points at --exclude-target-frameworks rather than --exclude-configs. Contract: - Components and projects carry the purl type their tool produces, from an exhaustive per-tool map, and a groupless nuget coordinate omits the namespace key instead of serializing an empty one. - Sidecar entries carry an ecosystem tag, so a nuget coordinate and a maven coordinate that share a name stay distinct. The producer always writes it; a payload without it is still valid and means maven. Shipping this needs the reachability sidecar's nuget schema change released first, because that consumer parses strictly. See docs/agents.md/repo/contract.md. - Resolution reports carry configsByProject, which attributes each resolved configuration to the project that resolved it. The flat union loses that as soon as two projects resolve different sets, which is routine for .NET. Config-name globs: - Compile include/exclude globs to anchored regex sources once, in config-glob.mts, and hand every emitter the compiled patterns. Removes the per-language globToRegex from the Gradle, sbt, and Maven emitters and adds a cross-language vector suite. Fail-closed fixes over the original branch: - The records writer forces LF. On Windows the default would glue a stray carriage return to each record's last field, flipping prod/direct flags, orphaning every edge, and failing every artifact path's exists-check, with the scan still reporting success. The records parser now also tolerates CRLF, so one emitter regressing cannot corrupt a scan. - A crash after partial output records a failure, so a truncated SBOM can no longer be published as a success. - The restore fallback record is gated on what was reported rather than on what a logger captured, so a restore whose every error was filtered as noise can no longer report a stale project.assets.json as a fresh scan. - PackageReference is checked before packages.config, matching NuGet's own precedence, so a leftover packages.config from a migration no longer wins over the project's real dependency graph. A project carrying both gets a warning.
Importing either build script as a library ran main() against the caller's argv. The dotnet build script lands with the guard already in place; these two are its siblings, and entry-scripts-are-fail-soft flags all three together.
Splitting artifact-paths out of assemble left the type import behind. The declaration build runs with noUnusedLocals, so it failed there while the type check passed.
…6.0.6 6.0.0 carries GHSA-68w7-72jg-6qpp, a critical NuGet client security-feature bypass, plus two high-severity advisories on NuGet.Common and NuGet.Protocol. Socket's own scanner blocks the PR on it. 6.0.6 is the patched floor of the same line, so the compile-low/run-high guarantee the references exist for is unchanged: the tool still binds on every SDK from 6 up and still ships no NuGet runtime assets.
ed4de97 to
154f939
Compare


.NET/NuGet joins the facts pipeline at parity with the JVM emitters. Original work by Jeppe Fredsgaard Blaabjerg (@jfblaa) in SocketDev/socket-cli#1404; this moves the reusable half into the repo that owns the emitters and the wire contracts, and the CLI command surface stays behind in socket-cli.
Warning
Do not release a version that emits the sidecar
ecosystemtag until the reachability sidecar consumer ships its nuget schema change. The scope is wider than it first looks: the producer stamps the tag on every entry, and the consumer parses the component object strictly, so a plain single-ecosystem Gradle or Maven reachability scan fails at the sidecar handoff too. Not just polyglot .NET+JVM ones.What this adds
A bundled C# tool that runs one MSBuild session per scan — evaluate, restore in-process, then read each
project.assets.jsonthrough NuGet's own APIs. Doing all three under a single global-property bag is the point: restore and the emitted graph can never end up describing different builds.runFactsGenerationnow acceptstool: 'dotnet', and the resolution report speaks NuGet. It says "target framework" and points at--exclude-target-frameworks, instead of telling a .NET user to pass a JVM option that does not exist.The C# tool ships no NuGet or MSBuild runtime assemblies — it compiles low and runs high against the SDK it finds
The tool targets net6.0 with
RollForward LatestMajorand marks every NuGet referenceExcludeAssets="runtime". At runtime the assemblies come from the locator-selected SDK.That is deliberate rather than incidental. The in-process restore loads the SDK's own
NuGet.Build.Tasks, and if the tool carried its own NuGet copies they would ref/def-clash with the SDK's on any machine whose NuGet version differs — a0x80131040failure that looks like a broken feed. Compiling against the oldest supported surface and letting the SDK provide the implementation binds correctly on every SDK from 6 up.Restore also forces
TreatWarningsAsErrors=false, so a project that promotes NuGet warnings to errors cannot abort the run.NU1902/NU1903/NU1904are security advisories, which a Socket scan should report as findings rather than choke on.Four fail-closed fixes on top of the original branch — each one is a case where a wrong scan could have exited green
The promise the tool's design leans on is that a wrong scan never exits 0. Four paths broke it.
\rglued to each record's last field flips prod/direct flags, orphans every edge, and fails every artifact path's exists-checkNewLine = "\n", and the parser now strips a trailing\rso one emitter regressing cannot corrupt a scanproject.assets.jsonthen read as a fresh scanpackages.configwas checked beforePackageReferencepackages.configgot scanned via the flat legacy path, reporting a stale pinned list as its closureThe first one has a regression test: a CRLF records stream now has to assemble byte-identically to an LF one.
One glob implementation instead of four — the emitters receive compiled patterns now
--include-configs/--exclude-configsglobs compile to anchored regex sources once, insrc/run/config-glob.mts, and every emitter is handed the compiled patterns. That deletes the hand-maintainedglobToRegexfrom the Gradle init script, the sbt plugin, and the Maven extension, which were three copies of one algorithm with nothing keeping them in sync.The emitted subset is restricted to what behaves identically in JS
RegExp, Javajava.util.regex, and .NETRegex, and a vector table pins the semantics all four consumers see.One intentional behavior change: an emitter that receives a pattern it cannot compile now drops it rather than degrading to a literal match. Since an empty include list means include-everything, a dropped include widens the scan. That is safe because every pattern this package emits is validated before it is sent, and it is documented in the module header for anyone driving a shipped emitter by hand.
Contract changes — an ecosystem tag, per-tool purl types, and per-project config attribution
ecosystemtag. A groupless NuGet id and a Maven artifactId can produce the same coordinate key, so without the tag they merge into one entry. The producer always writes it; a payload without it is still valid and meansmaven, which is exactly what every sidecar written before the tag looks like. Strict producer, liberal consumer.dotnet ? nuget : maventernary, so a fifth tool fails to type-check until someone names its type instead of silently assembling maven-typed components.namespaceinstead of serializing an empty one. Maven-type entries keep the empty key, which is the shape existing consumers match identity on.configsByProject. The flatscannedConfigsunion loses attribution as soon as two projects resolve different sets, which is routine for .NET where each project picks its own target frameworks.docs/agents.md/repo/contract.mdrecords why theecosystemaddition follows the coordinated-release rule rather than escaping it.Verification
Ran:
pnpm test(125 passed),pnpm run type,pnpm run lint --all,pnpm run check --all.Built and exercised the C# tool.
pnpm run build:dotnet-toolcompiles it against SDK 10.0.102, and running the built emitter against a scratch project produced an LF-only records file that this package's parser turned into a nuget-typed SBOM and an ecosystem-tagged sidecar passingvalidateResolvedPathsSidecar.check --allstatus: four checks fail, and all four already fail on the default branch —dispatch-table-is-current,prose-em-dash-chains-are-absent,coverage-badge-is-current, andcommits-have-no-ai-attribution, which refuses to report a pass on a shallow clone. This branch also clears a fifth that the default branch fails,entry-scripts-are-fail-soft, in its own commit.Worth a look before release: the build reports
NU1903/NU1904advisories on the pinned NuGet 6.0.0 packages. They areExcludeAssets="runtime"compile-time references so no vulnerable bytes ship, but the advisory is real.Left in socket-cli
The command surface —
cmd-manifest-dotnet, auto-manifest detection, the setup wizard,socket.jsondefaults, and the rollup/provenance wiring — stays with the CLI and can land as a small follow-up pinned against a facts release.Note
High Risk
Large new emitter plus sidecar
ecosystemon every entry requires a coordinated consumer schema release before shipping; mis-timed release breaks all strict sidecar parses including single-ecosystem JVM scans.Overview
Adds .NET/NuGet facts generation at parity with the JVM emitters:
runFactsGenerationacceptstool: 'dotnet', resolves the publishedsocket-facts-dotnetassembly viaassertDotnetToolBuilt, and runs a single-session MSBuild flow (evaluate → in-process restore → readproject.assets.json) that emits the same TSV records protocol. Packaging gainspnpm run build:dotnet-tool, publish-dir paths, and--require-builtgating for the dotnet artifact alongside Maven.Centralizes
--include-configs/--exclude-configssemantics insrc/run/config-glob.mts: globs compile once to anchored regex sources and are passed pre-compiled to Gradle, sbt, Maven, and the dotnet tool (replacing four duplicateglobToRegeximplementations).Extends wire contracts and assembly: NuGet components use purl type
nuget(groupless coords omitnamespace); resolution reports addconfigsByProject; sidecar entries always carryecosystemand accumulate byecosystem|coordso colliding Maven/NuGet names do not merge. NuGet-specific failure rendering uses target-framework wording and--exclude-target-frameworks. The records parser strips trailing\rfor CRLF safety.Fail-closed hardening in the C# emitter includes LF-only records, catastrophic-crash failure records, restore failure reporting vs filtered noise, and PackageReference-over-
packages.configprecedence matching NuGet.Reviewed by Cursor Bugbot for commit 93ea26c. Configure here.